package com.zrrd.straw1.service.impl;

import com.zrrd.straw1.entity.Tag;
import com.zrrd.straw1.mapper.TagMapper;
import com.zrrd.straw1.service.ITagService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;

@Service
public class TagServiceImpl extends ServiceImpl<TagMapper, Tag> implements ITagService {

    //准备一个集合用来保存数据库读取的标签列表
    //CopyOnWriteArrayList 是JUC（java.util.concurrent)包下的工具类
    //JUC包是针对多线程并发安全提供的一组工具类。
    //CopyOnWriteArrayList类的特点：
    //当有写操作时，会拷贝一个副本，保证所有线程读到的数据是一致的，安全的。
    //当前业务面对的问题就是大量用户同时访问。保证数据的可靠安全。

    /*
        什么情况下适合使用缓存机制 ？
        1.数据量不大
        2.数据几乎不修改，如果大量修改，是要保证和数据库同步的。
     */
    public final List<Tag> tags = new CopyOnWriteArrayList<>();

    /**
     * HashMap 是线程不安全的，在多线程（多用户同时访问），数据会发生错误。
     * 当前项目是在多线程环境下使用，就要考虑线程安全。
     * ConcurrentHashMap 是 JUC 包下的线程安全 Map 对象。
     */
    public final Map<String,Tag> name2TagMap = new ConcurrentHashMap<>();




    @Override
    public List<Tag> getTags( ) {
        /*
            MP (Mybatis-Plus) 已经提供了 ServiceImp<Mapper,Entity>提供大量两成的方法
            list() 查询所有
            当前类继承了 ServiceImp, 可以直接使用 MP 提供的方法。
            List 实现类是线程不安全的。所以要结果再保存到 CopyOnWriteArrayList 集合。
         */
        //如果 tags 第一次被访问，完成赋值操作。
        //第二次之后，直接返回 CopyOnWriteArrayList 中的内容。
        if(tags.isEmpty()) {
            //因为会存在有大量用户同时访问为空的情况，在访问数据库时上锁。达成同步效果。
            synchronized (tags) {
                //拿到锁之后，要冷静，再看一眼。
                if(tags.isEmpty()) {
                    tags.addAll( list() );

                    //同时给 name2TagMap 完成初始化
                    for( Tag t : tags){
                        name2TagMap.put( t.getName(), t ) ;
                    }

                }
            }
        }
        return tags ;
    }

    /**
     * 给控制层，或其它业务层实例提供 name2TagMap 缓存对象。
     * 比如给 QuestionServiceImpl 提供 map 缓存。
     * @return
     */
    @Override
    public Map<String, Tag> getName2TagMap() {
        //防止其它业务层调用时，name2TagMap 还未加载。
        if( name2TagMap.isEmpty()){
            //调用上面的getTags()方法
            //getTags方法会为name2TagMap赋值
            getTags();
        }
        return name2TagMap ;
    }
}
